/*!
\copyright  Copyright (c) 2022-2023 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief    Interfaces for the LE Audio Client
*/

#if defined(INCLUDE_LE_AUDIO_UNICAST_SOURCE) || defined(INCLUDE_LE_AUDIO_BROADCAST_SOURCE)
#include "le_audio_client.h"
#include "le_audio_client_messages.h"
#include "le_audio_client_context.h"
#include "tmap_client_source_unicast.h"
#ifdef INCLUDE_LE_AUDIO_BROADCAST_SOURCE
#include "le_audio_client_broadcast_private.h"
#include "le_audio_client_broadcast_audio_source.h"
#endif
#include "le_audio_client_unicast_music_source.h"
#include "le_audio_client_unicast_voice_source.h"
#include "le_audio_client_volume.h"

#include "system_state.h"
#include "le_audio_client_audio_config.h"
#include "cap_client_prim.h"
#include "gatt_connect.h"
#include "qualcomm_connection_manager.h"
#include "csip_client.h"
#include "tmap_profile.h"
#include "tmap_server_role.h"
#include "device_properties.h"
#include "audio_sources.h"
#include "voice_sources.h"
#include "bap_profile_client.h"
#include "vcp_profile_client.h"

#include <logging.h>
#include <panic.h>

#ifdef GC_SECTIONS
/* Move all functions in KEEP_PM section to ensure they are not removed during
 * garbage collection */
#pragma unitcodesection KEEP_PM
#endif


le_audio_client_context_t le_audio_client_context;

/*! Resets the LE Audio Client context */
static void leAudioClient_ResetContext(void)
{
    uint8 device_count;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    MessageCancelFirst(leAudioClient_GetTask(), LE_AUDIO_CLIENT_INTERNAL_QLL_CONNECT_TIMEOUT);
    leAudioClient_EndUnicastAudioSession(&client_ctxt->session_data);

    client_ctxt->gatt_cid = INVALID_CID;
    client_ctxt->group_handle = INVALID_GROUP_HANDLE;
    client_ctxt->total_devices = 0;
    client_ctxt->connected_devices = 0;
    client_ctxt->requested_audio_contexts = CAP_CLIENT_CONTEXT_TYPE_PROHIBITED;
    client_ctxt->configured_audio_contexts = CAP_CLIENT_CONTEXT_TYPE_PROHIBITED;
    client_ctxt->pts_mode = LE_AUDIO_CLIENT_PTS_MODE_OFF;
    LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_INITIALIZED);

    for(device_count = 0; device_count < LE_AUDIO_CLIENT_MAX_DEVICES_SUPPORTED; device_count++)
    {
        client_ctxt->gatt_cid_list[device_count] = INVALID_CID;
    }
}

/*! Check if compatible profile for the given context is connected or not */
static bool leAudioClient_IsProfileConnectedForAudioContext(CapClientContext audio_context)
{
    le_audio_client_profiles_t profile_to_use;

    profile_to_use = leAudioClient_GetProfileForAudioContext(audio_context);

    return profile_to_use == LE_AUDIO_CLIENT_PROFILE_TMAP ? TmapClientSource_IsTmapConnected() :
                                                            CapProfileClient_IsCapConnected();

}

/*! Get the next audio context to configure from the requested audio contexts list */
static bool leAudioClient_ConfigureNextAudioContext(ServiceHandle group_handle)
{
    CapClientContext context_to_configure;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();
    bool status = FALSE;
    uint8 config_index = 0;

    for (config_index = 0; config_index < LE_AUDIO_CLIENT_MAX_AUDIO_CONFIGS_SUPPORTED ; config_index++)
    {
        context_to_configure = leAudioClient_GetNextAudioContextToConfigure(client_ctxt->requested_audio_contexts);

        if (context_to_configure == CAP_CLIENT_CONTEXT_TYPE_PROHIBITED)
        {
            /* No more audio context to configure */
            break;
        }

        if (leAudioClient_IsProfileConnectedForAudioContext(context_to_configure))
        {
            /* Do a Codec & QoS config for the requested audio context */
            leAudioClient_ConfigureUnicastStreaming(group_handle, context_to_configure);
            status = TRUE;
            break;
        }
        else
        {
            DEBUG_LOG("leAudioClient_ConfigureNextAudioContext profile not connected for context 0x%x",
                       context_to_configure);

            /* Clear the ccontext from the requested list as not able to configure */
            client_ctxt->requested_audio_contexts &= (~context_to_configure);
        }
    }

    return status;
}

/*! Handles register with CAP confirmation message for TMAP */
static void leAudioClient_HandleTmapRegisterCapCfm(tmap_client_msg_status_t status)
{
    DEBUG_LOG("leAudioClient_HandleTmapRegisterCapCfm: status %d", status);

#ifdef INCLUDE_LE_AUDIO_BROADCAST_SOURCE
    PbpClientSource_RegisterTaskWithCap(leAudioClient_GetContext()->group_handle);
#endif /* INCLUDE_LE_AUDIO_BROADCAST_SOURCE */
}

void leAudioClient_HandleConfigComplete(ServiceHandle group_handle, uint16 audio_context_configured, bool config_complete_success)
{
    CapClientContext requested_audio_context;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    DEBUG_LOG("leAudioClient_HandleConfigComplete: context:0x%x success %d", audio_context_configured, config_complete_success);

    requested_audio_context = leAudioClient_GetNextAudioContextToConfigure(client_ctxt->requested_audio_contexts);

    if (config_complete_success)
    {
        /* Configuration is successfull. Update the configured list */
        PanicFalse(requested_audio_context == audio_context_configured);
        client_ctxt->configured_audio_contexts |= audio_context_configured;
    }

    /* Clear the audio context from requested list */
    client_ctxt->requested_audio_contexts &= (~requested_audio_context);

    /* Configure the next audio context in the requested list (if any) */
    if (!leAudioClient_ConfigureNextAudioContext(group_handle))
    {
        /* There is no more configuration needs to be done. Move the state to connected */
        LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTED);
        leAudioClientMessages_SendConnectInd(group_handle, LE_AUDIO_CLIENT_STATUS_SUCCESS,
                                             client_ctxt->total_devices, client_ctxt->connected_devices);
    }
}

/* Handle CAP Profile messages in connecting state */
static void leAudioClient_HandleCapMessagesInConnectingState(const cap_profile_client_msg_t *message)
{
    uint8 device_count;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    switch (message->id)
    {
        case CAP_PROFILE_CLIENT_MSG_ID_INIT_COMPLETE:
        {
            const CAP_PROFILE_CLIENT_INIT_COMPLETE_T *cfm = &message->body.init_complete;

            DEBUG_LOG("leAudioCLient_HandleCapMessagesInConnectingState: CAP_PROFILE_CLIENT_INIT_COMPLETE Status %d", cfm->status);

            if (cfm->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS)
            {
                /* All devices in the group got added successfully. */
                leAudioClient_GetContext()->group_handle = cfm->group_handle;
                leAudioClient_GetContext()->total_devices = cfm->total_devices;
                leAudioClient_GetContext()->connected_devices = cfm->connected_devices;

                /* CAP is initialised, now try to initialise TMAP. Panic if TMAP init request not able to place */
                PanicFalse(TmapClientSource_CreateInstance(leAudioClient_GetContext()->gatt_cid));
                TmapClientSource_SetGroupId(leAudioClient_GetContext()->gatt_cid, cfm->group_handle);

                /* Send indication that device got added successfully and no other device pending to add */
                leAudioClientMessages_SendDeviceAddedInd(leAudioClient_GetContext()->gatt_cid, TRUE, FALSE);
            }
            else
            {
                /* LE Audio Client failed to connect. Reset the context */
                leAudioClient_ResetContext();

                /* Send connection failure indication */
                leAudioClientMessages_SendDeviceAddedInd(leAudioClient_GetContext()->gatt_cid, FALSE, TRUE);
                leAudioClientMessages_SendConnectInd(cfm->group_handle, LE_AUDIO_CLIENT_STATUS_FAILED, 0, 0);
            }
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_DEVICE_ADDED:
        {
            const CAP_PROFILE_CLIENT_DEVICE_ADDED_T *cfm = &message->body.device_added;

            DEBUG_LOG("leAudioCLient_HandleCapMessagesInConnectingState: CAP_PROFILE_CLIENT_MSG_ID_DEVICE_ADDED Status %d", cfm->status);

            if (cfm->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS)
            {
                leAudioClient_GetContext()->group_handle = cfm->group_handle;

                for(device_count = 0; device_count < LE_AUDIO_CLIENT_MAX_DEVICES_SUPPORTED; device_count++)
                {
                    /* Add the CID of the new added device to an empty slot */
                    if (client_ctxt->gatt_cid_list[device_count] == INVALID_CID)
                    {
                        client_ctxt->gatt_cid_list[device_count] = cfm->cid;
                        break;
                    }
                }
            }
            leAudioClientMessages_SendDeviceAddedInd(leAudioClient_GetContext()->gatt_cid, cfm->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS,
                                                     cfm->more_devices_needed);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_DEVICE_REMOVED:
        {
            const CAP_PROFILE_CLIENT_DEVICE_REMOVED_T *cfm = &message->body.device_removed;

            DEBUG_LOG("leAudioCLient_HandleCapMessagesInConnectingState: CAP_PROFILE_CLIENT_MSG_ID_DEVICE_REMOVED Status %d", cfm->status);

            leAudioClientMessages_SendDeviceRemovedInd(cfm->cid, cfm->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS,
                                                       cfm->more_devices_present);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_PROFILE_DISCONNECT:
        {
            const CAP_PROFILE_CLIENT_DISCONNECT_T *cfm = &message->body.disconnect_complete;

            DEBUG_LOG("leAudioCLient_HandleCapMessagesInConnectingState: CAP_PROFILE_CLIENT_MSG_ID_PROFILE_DISCONNECT Status %d", cfm->status);

            /* Both profiles disconnected */
            leAudioClientMessages_SendConnectInd(cfm->group_handle, LE_AUDIO_CLIENT_STATUS_FAILED, 0, 0);
            leAudioClient_ResetContext();
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE:
        {
            const CAP_PROFILE_CLIENT_UNICAST_CONFIG_COMPLETE_T *cfm = &message->body.unicast_config_complete;

            leAudioClient_HandleConfigComplete(cfm->group_handle, cfm->audio_context, cfm->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS);
        }
        break;

        default:
        break;
    }
}

/* Handle CAP Profile messages in disconnecting state */
static void leAudioClient_HandleCapMessagesInDisconnectingState(const cap_profile_client_msg_t *message)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    switch (message->id)
    {
        case CAP_PROFILE_CLIENT_MSG_ID_PROFILE_DISCONNECT:
        {
            const CAP_PROFILE_CLIENT_DISCONNECT_T *cfm = &message->body.disconnect_complete;

            DEBUG_LOG("leAudioClient_HandleCapMessagesInDisconnectingState: CAP_PROFILE_CLIENT_DISCONNECT_T Status %d", cfm->status);

            if (TmapClientSource_IsTmapConnected())
            {
                /* CAP disconnected. Now disconnect TMAP */
                PanicFalse(TmapClientSource_DestroyInstance(cfm->group_handle, 0));
            }
            else
            {
                /* Both profiles disconnected */
                leAudioClientMessages_SendDisconnectInd(cfm->group_handle, cfm->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS);
                leAudioClient_ResetContext();
            }

            /* On disconnection, reset the streaming flag(if active) in unicast mode */
            if (leAudioClient_IsInUnicastStreaming())
            {
                leAudioClient_ClearStreamingState();
            }
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_DEVICE_REMOVED:
        {
            const CAP_PROFILE_CLIENT_DEVICE_REMOVED_T *cfm = &message->body.device_removed;

            DEBUG_LOG("leAudioClient_HandleCapMessagesInDisconnectingState: CAP_PROFILE_CLIENT_MSG_ID_DEVICE_REMOVED Status %d", cfm->status);

            /* Send indication to registered clients that device is removed only if TMAP is not connected. If TMAP is connected,
               this is done when corresponding TMAP instance gets removed */
            if (!TmapClientSource_IsTmapConnectedForCid(cfm->cid))
            {
                leAudioClientMessages_SendDeviceRemovedInd(cfm->cid, cfm->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS, cfm->more_devices_present);
            }
            else
            {
                if (!cfm->more_devices_present)
                {
                     /* All CAP instances in the group is removed. TMAP disconnect will be trigered when we receive
                        CAP_PROFILE_CLIENT_MSG_ID_PROFILE_DISCONNECT. */
                    return;
                }

                /* Disconnect the TMAP instance as CAP is now disconected */
                PanicFalse(TmapClientSource_DestroyInstance(client_ctxt->group_handle, cfm->cid));
            }
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_STREAM_STOP:
        {
            const CAP_PROFILE_CLIENT_UNICAST_STREAM_STOP_T *cfm = &message->body.unicast_stream_stop;

            DEBUG_LOG("leAudioClient_HandleCapMessagesInDisconnectingState: CAP_PROFILE_CLIENT_MSG_ID_UNICAST_STREAM_STOP Status %d", cfm->status);

            if (client_ctxt->session_data.release_config)
            {
                PanicFalse(CapProfileClient_StopUnicastStreaming(client_ctxt->group_handle, TRUE));
                client_ctxt->session_data.release_config = FALSE;
            }
            else
            {
                CapProfileClient_DestroyInstance(client_ctxt->group_handle, client_ctxt->gatt_cid);
            }
        }
        break;

        default:
        break;
    }
}

/* Handle TMAP Profile generic messages in connecting state */
static void leAudioClient_HandleTmapMessagesInConnectingState(const tmap_client_msg_t *message)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    switch (message->id)
    {
        case TMAP_CLIENT_MSG_ID_INIT_COMPLETE:
        {
            const TMAP_CLIENT_MSG_ID_INIT_COMPLETE_T *cfm = &message->body.init_complete;

            DEBUG_LOG("leAudioClient_HandleTmapMessagesInConnectingState: TMAP_CLIENT_MSG_ID_INIT_COMPLETE Status %d", cfm->status);

            /* Treat TMAS service not present also as success because profiles will be internally routing all TMAP operation
               through CAP if TMAS service is not present. */
            if (cfm->status == TMAP_CLIENT_MSG_STATUS_SUCCESS || cfm->status == TMAP_CLIENT_MSG_STATUS_SUCCESS_TMAS_SRVC_NOT_FOUND)
            {
                TmapClientSource_RegisterTaskWithCap(cfm->group_handle);

                if (client_ctxt->connected_devices > 1)
                {
                    /* If there is more than one member connected, then need to add those devices into the TMAP group */
                    TmapClientSource_AddDeviceToGroup(&client_ctxt->gatt_cid_list[0], client_ctxt->connected_devices);
                }

                /* Now we need to configure profiles according to the contexts */
                if (client_ctxt->requested_audio_contexts != CAP_CLIENT_CONTEXT_TYPE_PROHIBITED)
                {
                    if (MessagePendingFirst(leAudioClient_GetTask(), LE_AUDIO_CLIENT_INTERNAL_QLL_CONNECT_TIMEOUT, NULL))
                    {
                        LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_WAITING_FOR_QLL_TO_CONNECT);
                        return;
                    }
                    else
                    {
                        /* Configure the first audio context from the requested contexts list */
                        if (!leAudioClient_ConfigureNextAudioContext(leAudioClient_GetContext()->group_handle))
                        {
                            /* There is no pending configuration. Move the state to connected */
                            LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTED);
                            leAudioClientMessages_SendConnectInd(cfm->group_handle, LE_AUDIO_CLIENT_STATUS_SUCCESS,
                                                                 client_ctxt->total_devices, client_ctxt->connected_devices);
                        }
                    }
                }
                else
                {
                    /* There is nothing to configure for TMAP/CAP. Move the state to connected */
                    LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTED);
                    leAudioClientMessages_SendConnectInd(cfm->group_handle, LE_AUDIO_CLIENT_STATUS_SUCCESS,
                                                         client_ctxt->total_devices, client_ctxt->connected_devices);
                }
            }
            else
            {
                DEBUG_LOG("leAudioClient_HandleTmapMessagesInConnectingState: TMAP init failed");

                /* Reaching here indicates that TMAP init got failed but CAP init was successful.
                   Eg. LE ACL gets disconnected immediately after CAP init got successful.
                   So destroy the CAP instance and send connect failure indication */
                PanicFalse(CapProfileClient_DestroyInstance(client_ctxt->group_handle, client_ctxt->gatt_cid));
            }
        }
        break;

        case TMAP_CLIENT_MSG_ID_REGISTER_CAP_CFM:
        {
            const TMAP_CLIENT_MSG_ID_REGISTER_CAP_CFM_T *cfm = &message->body.cap_register_cfm;

            leAudioClient_HandleTmapRegisterCapCfm(cfm->status);
        }
        break;

        default:
        break;
    }
}

/* Handle TMAP Profile generic messages in disconnecting state */
static void leAudioClient_HandleTmapMessagesInDisconnectingState(const tmap_client_msg_t *message)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    switch (message->id)
    {
        case TMAP_CLIENT_MSG_ID_PROFILE_DISCONNECT:
        {
            const TMAP_CLIENT_MSG_ID_PROFILE_DISCONNECT_T *cfm = &message->body.disconnect_complete;

            DEBUG_LOG("leAudioClient_HandleTmapMessagesInDisconnectingState: TMAP_CLIENT_MSG_ID_PROFILE_DISCONNECT Status %d", cfm->status);

            if (CapProfileClient_IsCapConnected())
            {
                /* TMAP disconnected. Now disconnect CAP */
                PanicFalse(CapProfileClient_DestroyInstance(client_ctxt->group_handle, client_ctxt->gatt_cid));
            }
            else
            {
                /* Both profiles disconnected */
                leAudioClientMessages_SendDisconnectInd(client_ctxt->gatt_cid, cfm->status == TMAP_CLIENT_MSG_STATUS_SUCCESS);
                leAudioClient_ResetContext();
            }

            /* TMAP profile gets disconnected. Reset the broadcast source related parameters */
            leAudioClientBroadcast_ResetSourceContext();
        }
        break;

        case TMAP_CLIENT_MSG_ID_PROFILE_DEVICE_REMOVED:
        {
            const TMAP_CLIENT_MSG_ID_PROFILE_DEVICE_REMOVED_T *cfm = &message->body.device_removed;

            DEBUG_LOG("leAudioClient_HandleTmapMessagesInDisconnectingState: TMAP_CLIENT_MSG_ID_PROFILE_DEVICE_REMOVED Status %d", cfm->status);

            leAudioClient_GetContext()->connected_devices--;

            /* Send indication to registered clients that device is removed only if CAP is not connected. If CAP is connected,
               this is done when corresponding CAP instance gets removed */
            if (!CapProfileClient_IsCapConnectedForCid(cfm->cid))
            {
                /* Destroy the instance of given cid alone */
                leAudioClientMessages_SendDeviceRemovedInd(cfm->cid, cfm->status == TMAP_CLIENT_MSG_STATUS_SUCCESS, cfm->more_devices_present);
            }
            else
            {
                if (!cfm->more_devices_present)
                {
                     /* All CAP instances in the group is removed. TMAP disconnect will be trigered when we receive
                        TMAP_CLIENT_MSG_ID_PROFILE_DISCONNECT. */
                    return;
                }

                /* Disconnect the CAP instance as TMAP is now disconected */
                PanicFalse(CapProfileClient_DestroyInstance(client_ctxt->group_handle, cfm->cid));
            }
        }
        break;

        default:
        break;
    }
}

/* Process all the TMAP profile generic messages */
static void leAudioClient_ProcessTmapMessage(const tmap_client_msg_t *message)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (client_ctxt->state < LE_AUDIO_CLIENT_STATE_CONNECTED)
    {
        return;
    }

    switch (message->id)
    {
        case TMAP_CLIENT_MSG_ID_REGISTER_CAP_CFM:
        {
            const TMAP_CLIENT_MSG_ID_REGISTER_CAP_CFM_T *cfm = &message->body.cap_register_cfm;

            leAudioClient_HandleTmapRegisterCapCfm(cfm->status);
        }
        break;

        case TMAP_CLIENT_MSG_ID_VOLUME_STATE_IND:
        {
            leAudioClient_HandleUnicastVolumeStateInd(message->body.volume_state_ind.volumeState);
        }
        break;

        default:
        break;
    }
}

/*! \brief Process CAP Domain Messages */
static void leAudioClient_ProcessCapClientMessage(ServiceHandle group_handle, const cap_profile_client_msg_t *message)
{
    UNUSED(group_handle);

    switch (LeAudioClient_GetState())
    {
        case LE_AUDIO_CLIENT_STATE_CONNECTING:
            leAudioClient_HandleCapMessagesInConnectingState(message);
        break;

        case LE_AUDIO_CLIENT_STATE_DISCONNECTING:
            leAudioClient_HandleCapMessagesInDisconnectingState(message);
        break;

        case LE_AUDIO_CLIENT_STATE_CONNECTED:
            leAudioClient_ProcessCapUnicastMessage(message);
        break;

        default:
        break;
    }
}

/*! \brief Process TMAP Domain generic Messages */
static void leAudioClient_ProcessTmapClientMessage(const tmap_client_msg_t *message)
{
    switch (LeAudioClient_GetState())
    {
        case LE_AUDIO_CLIENT_STATE_CONNECTING:
        case LE_AUDIO_CLIENT_STATE_WAITING_FOR_QLL_TO_CONNECT:
            leAudioClient_HandleTmapMessagesInConnectingState(message);
        break;

        case LE_AUDIO_CLIENT_STATE_DISCONNECTING:
            leAudioClient_HandleTmapMessagesInDisconnectingState(message);
        break;

        case LE_AUDIO_CLIENT_STATE_CONNECTED:
            leAudioClient_ProcessTmapMessage(message);
        break;

        default:
        break;
    }
}

/* Handle CSIP Profile messages in connecting CSIP state */
static void leAudioClient_HandleCsipMessagesInConnectingState(const csip_client_msg_t *message)
{
    switch (message->id)
    {
        case CSIP_CLIENT_MSG_ID_DEVICE_ADDED:
        {
            const CSIP_CLIENT_DEVICE_ADDED_T *cfm = &message->body.device_added;

            DEBUG_LOG("leAudioClient_HandleCapMessagesInConnectingCsipState: CSIP_CLIENT_MSG_ID_DEVICE_ADDED Status %d",
                       cfm->status);

            leAudioClientMessages_SendDeviceAddedInd(cfm->cid, cfm->status == CSIP_CLIENT_STATUS_SUCCESS,
                                                     cfm->more_devices_needed);
        }
        break;

        case CSIP_CLIENT_MSG_ID_INIT_COMPLETE:
        {
            const CSIP_CLIENT_INIT_COMPLETE_T *cfm = &message->body.init_complete;

            DEBUG_LOG("leAudioClient_HandleCapMessagesInConnectingCsipState: CSIP_CLIENT_MSG_ID_INIT_COMPLETE Status %d",
                       cfm->status);

            leAudioClientMessages_SendConnectInd(0,
                                                 cfm->status == CSIP_CLIENT_STATUS_SUCCESS ? LE_AUDIO_CLIENT_STATUS_SUCCESS :
                                                                                             LE_AUDIO_CLIENT_STATUS_FAILED,
                                                 cfm->total_devices,
                                                 cfm->connected_devices);
        }
        break;

        case CSIP_CLIENT_MSG_ID_DEVICE_REMOVED:
        {
            const CSIP_CLIENT_DEVICE_REMOVED_T *cfm = &message->body.device_removed;

            DEBUG_LOG("leAudioClient_HandleCapMessagesInConnectingCsipState: CSIP_CLIENT_MSG_ID_DEVICE_REMOVED Status %d",
                       cfm->status);

            leAudioClientMessages_SendDeviceRemovedInd(cfm->cid, cfm->status == CSIP_CLIENT_STATUS_SUCCESS, cfm->more_devices_present);
        }
        break;

        case CSIP_CLIENT_MSG_ID_PROFILE_DISCONNECT:
        {
            const CSIP_CLIENT_DISCONNECT_T *cfm = &message->body.disconnected;

            DEBUG_LOG("leAudioClient_HandleCapMessagesInConnectingCsipState: CSIP_CLIENT_MSG_ID_PROFILE_DISCONNECT Status %d",
                       cfm->status);

            leAudioClientMessages_SendDisconnectInd(0, cfm->status == CSIP_CLIENT_STATUS_SUCCESS);
        }
        break;

        default:
            DEBUG_LOG("leAudioClient_HandleCapMessagesInConnectingCsipState: unhandled message 0x%x", message->id);
        break;
    }
}

/*! \brief Process CSIP Domain Messages */
static void leAudioClient_ProcessCsipClientMessage(const csip_client_msg_t *message)
{
    switch (LeAudioClient_GetState())
    {
        case LE_AUDIO_CLIENT_STATE_CONNECTING:
            leAudioClient_HandleCsipMessagesInConnectingState(message);
        break;

        default:
        break;
    }
}

/* Handle BAP Profile messages in connecting BAP state */
static void leAudioClient_HandleBapMessagesInConnectingState(const bap_profile_client_msg_t *message)
{
    switch (message->id)
    {
        case BAP_PROFILE_CLIENT_MSG_ID_INIT_COMPLETE:
        {
            const BAP_PROFILE_CLIENT_INIT_COMPLETE_T *cfm = &message->body.init_complete;

            DEBUG_LOG("leAudioClient_HandleBapMessagesInConnectingBapState: BAP_PROFILE_CLIENT_MSG_ID_INIT_COMPLETE Status %d",
                       cfm->status);

            if (cfm->status == BAP_PROFILE_CLIENT_STATUS_SUCCESS)
            {
                if (cfm->more_devices_needed)
                {
                    leAudioClientMessages_SendDeviceAddedInd(leAudioClient_GetContext()->gatt_cid,
                                                             TRUE,
                                                             cfm->more_devices_needed);
                }
                else
                {
                    leAudioClientMessages_SendConnectInd(0, LE_AUDIO_CLIENT_STATUS_SUCCESS, 1, 1);
                    LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTED);
                }
            }
            else
            {
                leAudioClientMessages_SendConnectInd(0, LE_AUDIO_CLIENT_STATUS_FAILED, 1, 1);
                leAudioClient_ResetContext();
            }
        }
        break;

        default:
            DEBUG_LOG("leAudioClient_HandleBapMessagesInConnectingState: unhandled message 0x%x", message->id);
        break;
    }
}

/* Handle BAP Profile messages in connected BAP state */
static void leAudioClient_HandleBapMessagesInConnectedState(const bap_profile_client_msg_t *message)
{
    switch (message->id)
    {
        case BAP_PROFILE_CLIENT_MSG_ID_PROFILE_DISCONNECT:
        {
            const BAP_PROFILE_CLIENT_DISCONNECT_T *cfm = &message->body.disconnected;

            DEBUG_LOG("leAudioClient_HandleBapMessagesInConnectedState: BAP_PROFILE_CLIENT_MSG_ID_PROFILE_DISCONNECT Status %d",
                       cfm->status);

            if (cfm->connected_server_cnt == 0)
            {
                leAudioClientMessages_SendDisconnectInd(0, cfm->status == BAP_PROFILE_CLIENT_STATUS_SUCCESS);
                leAudioClient_ResetContext();
            }
        }
        break;

        default:
            DEBUG_LOG("leAudioClient_HandleBapMessagesInConnectedState: unhandled message 0x%x", message->id);
        break;
    }
}

/*! \brief Process BAP Domain Messages */
static void leAudioClient_ProcessBapClientMessage(const bap_profile_client_msg_t *message)
{
    switch (LeAudioClient_GetState())
    {
        case LE_AUDIO_CLIENT_STATE_CONNECTING:
            leAudioClient_HandleBapMessagesInConnectingState(message);
        break;

        case LE_AUDIO_CLIENT_STATE_CONNECTED:
            leAudioClient_HandleBapMessagesInConnectedState(message);
        break;

        default:
        break;
    }
}

static void leAudioClient_ConfigureStreamIfWaitingForQllEvent(void)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (le_audio_client_context.state == LE_AUDIO_CLIENT_STATE_WAITING_FOR_QLL_TO_CONNECT)
    {
        LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTING);

        /* Configure the first default audio context from the requested contexts list */
        if (!leAudioClient_ConfigureNextAudioContext(client_ctxt->group_handle))
        {
            /* There is no pending configuration. Move the state to connected */
            LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTED);
            leAudioClientMessages_SendConnectInd(client_ctxt->group_handle, LE_AUDIO_CLIENT_STATUS_SUCCESS,
                                                 client_ctxt->total_devices, client_ctxt->connected_devices);
        }
    }
}

static void leAudioClient_HandleQcomRemoteIsoCapableMessage(const QCOM_CON_MANAGER_REMOTE_ISO_QHS_CAPABLE_IND_T *msg)
{
    DEBUG_LOG("leAudioClient_HandleQcomRemoteIsoCapableMessage lap=%06lX", msg->tp_addr.taddr.addr.lap);

    le_audio_client_context.iso_qhs_supported = TRUE;

    MessageCancelFirst(leAudioClient_GetTask(), LE_AUDIO_CLIENT_INTERNAL_QLL_CONNECT_TIMEOUT);
    leAudioClient_ConfigureStreamIfWaitingForQllEvent();
}

static void leAudioClient_HandleQllConnectionTimeout(void)
{
    DEBUG_LOG("leAudioClient_HandleQllConnectionTimeout");
    leAudioClient_ConfigureStreamIfWaitingForQllEvent();
}

static void leAudioClient_HandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);
    DEBUG_LOG("leAudioClient_HandleMessage Received Message Id : 0x%x", id);

    if (leAudioClientBroadcast_ProcessMsgIfFromBcastRouter(id, message))
    {
        return;
    }

    switch (id)
    {
        case QCOM_CON_MANAGER_REMOTE_ISO_QHS_CAPABLE_IND:
            leAudioClient_HandleQcomRemoteIsoCapableMessage(message);
        break;

        case LE_AUDIO_CLIENT_INTERNAL_QLL_CONNECT_TIMEOUT:
            leAudioClient_HandleQllConnectionTimeout();
        break;

#ifdef INCLUDE_LE_AUDIO_UNICAST_SOURCE
        case TMAP_SERVER_REMOTE_CALL_CONTROL_CALL_ACCEPT:
            leAudioClientMessages_HandleRemoteCallControlAccept();
        break;

        case TMAP_SERVER_REMOTE_CALL_CONTROL_CALL_TERMINATE:
            leAudioClientMessages_HandleRemoteCallControlTerminate();
        break;
#endif

        default:
        break;
    }
}

static bool leAudioClient_ConnectLeaProfileForPts(gatt_cid_t cid)
{
    bool status = FALSE;

    switch (leAudioClient_GetContext()->pts_mode)
    {
        case LE_AUDIO_CLIENT_PTS_MODE_CSIP:
        {
            if (CsipClient_CreateInstance(cid))
            {
                LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTING);
                status = TRUE;
            }
        }
        break;

        case LE_AUDIO_CLIENT_PTS_MODE_BAP:
        {
            if (BapProfileClient_CreateInstance(cid, leAudioClient_ProcessBapClientMessage))
            {
                LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTING);
                status = TRUE;
            }
        }
        break;

        case LE_AUDIO_CLIENT_PTS_MODE_VCP:
        {
            if (VcpProfileClient_CreateInstance(cid))
            {
                leAudioClientMessages_SendConnectInd(0, LE_AUDIO_CLIENT_STATUS_SUCCESS, 1, 1);
                LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTED);
                status = TRUE;
            }
        }
        break;

        case LE_AUDIO_CLIENT_PTS_MODE_GMCS:
        case LE_AUDIO_CLIENT_PTS_MODE_GTBS:
        {
           LeTmapServer_EnablePtsMode(TRUE);
        }
        break;

        default:
        break;
    }

    DEBUG_LOG_INFO("leAudioClient_ConnectLeaProfileForPts: enum:le_audio_client_pts_mode_t:%d connection status %d",
                   leAudioClient_GetContext()->pts_mode, status);

    return status;
}

static bool leAudioClient_CapConnect(gatt_cid_t cid)
{
    bool status = FALSE;

    /* Panic if all devices in the group is already connected */
    PanicFalse(leAudioClient_GetContext()->connected_devices == 0 ||
               leAudioClient_GetContext()->connected_devices != leAudioClient_GetContext()->total_devices);

    if (leAudioClient_GetContext()->group_handle != INVALID_GROUP_HANDLE)
    {
        if (CapProfileClient_AddDeviceToGroup(le_audio_client_context.group_handle, cid))
        {
            le_audio_client_context.gatt_cid = cid;
            status = TRUE;
        }
    }
    else
    {
        PanicFalse(le_audio_client_context.gatt_cid == INVALID_CID);

        le_audio_client_context.gatt_cid = cid;

        /* Set the default audio contexts to configure */
        le_audio_client_context.configured_audio_contexts = 0;

#ifdef INCLUDE_QCOM_CON_MANAGER
        if (!le_audio_client_context.iso_qhs_supported)
        {
            MessageCancelFirst(leAudioClient_GetTask(), LE_AUDIO_CLIENT_INTERNAL_QLL_CONNECT_TIMEOUT);
            MessageSendLater(leAudioClient_GetTask(), LE_AUDIO_CLIENT_INTERNAL_QLL_CONNECT_TIMEOUT, NULL,
                             leAudioClient_QllConnectTimeout());
        }
#endif

        if (CapProfileClient_CreateInstance(cid))
        {
            LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_CONNECTING);
            status = TRUE;
        }

        DEBUG_LOG_INFO("LeAudioClient_CapConnect: Connecting & Configuring for audio_contexts 0x%x status %d",
                       le_audio_client_context.requested_audio_contexts, status);
    }

    return status;
}

static bool leAudioClient_GetSpeakerPathConfig(gatt_cid_t cid, void *media_config)
{
    bool status = FALSE;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    switch (client_ctxt->pts_mode)
    {
        case LE_AUDIO_CLIENT_PTS_MODE_BAP:
        {
            status = BapProfileClient_GetSpeakerPathConfig(cid, (bap_media_config_t*) media_config);
        }
        break;

        case LE_AUDIO_CLIENT_PTS_MODE_TMAP:
        {
            status = TmapClientSource_PtsGetSpeakerPathConfig(cid, (tmap_media_config_t*) media_config);
        }
        break;

        default:
        break;
    }

    return status;
}

static bool leAudioClient_GetMicPathConfig(gatt_cid_t cid, void *mic_config)
{
    bool status = FALSE;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    switch (client_ctxt->pts_mode)
    {
        case LE_AUDIO_CLIENT_PTS_MODE_BAP:
        {
            status = BapProfileClient_GetMicPathConfig(cid, (bap_microphone_config_t*) mic_config);
        }
        break;

        case LE_AUDIO_CLIENT_PTS_MODE_TMAP:
        {
            status = TmapClientSource_PtsGetMicPathConfig(cid, (tmap_microphone_config_t*) mic_config);
        }
        break;

        default:
        break;
    }

    return status;
}

bool LeAudioClient_Init(Task init_task)
{
    uint8 cid_index;
    UNUSED(init_task);

    DEBUG_LOG("LeAudioClient_Init");

    memset(&le_audio_client_context, 0, sizeof(le_audio_client_context));
    le_audio_client_context.client_tasks = TaskList_Create();
    le_audio_client_context.gatt_cid = INVALID_CID;
    le_audio_client_context.group_handle = INVALID_GROUP_HANDLE;
    le_audio_client_context.mode = LE_AUDIO_CLIENT_MODE_UNICAST;

    leAudioClient_GetTask()->handler = leAudioClient_HandleMessage;

    for (cid_index = 0; cid_index < LE_AUDIO_CLIENT_MAX_DEVICES_SUPPORTED; cid_index++)
    {
        le_audio_client_context.session_data.devices_cis_info[cid_index].cid = INVALID_CID;
        le_audio_client_context.gatt_cid_list[cid_index] = INVALID_CID;
    }

    /* Register as observer to CAP profile to receive CAP events */
    CapProfileClient_Init(leAudioClient_ProcessCapClientMessage);
    CsipClient_Init(leAudioClient_ProcessCsipClientMessage);

    TmapProfile_Init();
    TmapClientSource_Init(NULL);
    TmapClientSource_RegisterCallback(leAudioClient_ProcessTmapClientMessage);

#ifdef INCLUDE_LE_AUDIO_UNICAST_SOURCE
    LeTmapServer_RegisterForRemoteCallControls(leAudioClient_GetTask());
    TmapClientSourceUnicast_RegisterCallback(leAudioClient_ProcessTmapClientUnicastMessage);
    leAudioClient_InitMusicSource();
    leAudioClient_InitVoiceSource();
#endif
    leAudioClient_AudioConfigInit();
    LeAudioClientVolume_Init();

    QcomConManagerRegisterClient(leAudioClient_GetTask());

#ifdef INCLUDE_LE_AUDIO_BROADCAST_SOURCE
    /* Broadcast source needs to be initialised as part of LE audio initialisation */
    leAudioClientBroadcast_Init();
    leAudioClient_InitBroadcastAudioSource();
    leAudioClient_SetBroadcastAudioConfigType(LE_AUDIO_CLIENT_BROADCAST_CONFIG_TYPE_HQ);
#else
    LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_INITIALIZED);

    /* Inform application that LE audio client init is completed */
    MessageSend(SystemState_GetTransitionTask(), LE_AUDIO_CLIENT_INIT_CFM, NULL);
#endif

    return TRUE;
}

/* Sets the default audio context. This will override any config done using
   LeAudioClient_SetBootGameModeConfig() */
static void leAudioClient_SetDefaultAudioContext(uint16 default_context)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    client_ctxt->requested_audio_contexts = default_context;
}

void LeAudioClient_SetState(le_audio_client_state_t state)
{
    leAudioClient_GetContext()->state = state;

    if (state == LE_AUDIO_CLIENT_STATE_CONNECTED &&
        !LeAudioClient_IsInUnicastMode() &&
         leAudioClient_IsStreamingEnabled())
    {
        /* Add source to assistant if not already added */
        leAudioClientBroadcast_AddSourceToAssistant();
    }
}

bool LeAudioClient_Connect(gatt_cid_t cid)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    switch (client_ctxt->pts_mode)
    {
        case LE_AUDIO_CLIENT_PTS_MODE_CAP:
        case LE_AUDIO_CLIENT_PTS_MODE_TMAP:
        {
            /* Set the default audio context to zero so that it will not get
               configured as part of connection */
            leAudioClient_SetDefaultAudioContext(0);
        }
        /* Fall through */
        case LE_AUDIO_CLIENT_PTS_MODE_OFF:
        {
            return leAudioClient_CapConnect(cid);
        }

        default:
        {
            return leAudioClient_ConnectLeaProfileForPts(cid);
        }
    }
}

bool LeAudioClient_DeviceDiscoveryFailed(void)
{
    bool status = FALSE;

    if (leAudioClient_GetContext()->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_CSIP)
    {
        /* Todo : Need to handle this scenario */
        return TRUE;
    }

    DEBUG_LOG_INFO("LeAudioClient_DeviceDiscoveryFailed");

    status = CapProfileClient_CompleteInitWithExistingDevices(leAudioClient_GetContext()->group_handle);

    return status;
}

static void leAudioClient_HandleDisconnectRequestInPtsMode(gatt_cid_t cid)
{
    switch (leAudioClient_GetContext()->pts_mode)
    {
        case LE_AUDIO_CLIENT_PTS_MODE_CSIP:
        {
           if (!CsipClient_DestroyInstance(cid))
           {
                /* Todo: Destroy failed mostly because group disconnect is not implemented in CSIP.
                   Needs to handle it. */
                leAudioClientMessages_SendDisconnectInd(0, LE_AUDIO_CLIENT_STATUS_FAILED);
           }
        }
        break;

        case LE_AUDIO_CLIENT_PTS_MODE_BAP:
        {
            if (!BapProfileClient_DestroyInstance(cid))
            {
                leAudioClientMessages_SendDisconnectInd(0, LE_AUDIO_CLIENT_STATUS_FAILED);
                leAudioClient_ResetContext();
            }
        }
        break;

        case LE_AUDIO_CLIENT_PTS_MODE_VCP:
        {
            if (!VcpProfileClient_DestroyInstance(cid))
            {
                leAudioClientMessages_SendDisconnectInd(0, LE_AUDIO_CLIENT_STATUS_FAILED);
                leAudioClient_ResetContext();
            }
            else
            {
                leAudioClientMessages_SendDisconnectInd(0, BAP_PROFILE_CLIENT_STATUS_SUCCESS);
                leAudioClient_ResetContext();
            }
        }
        break;

        default:
        break;
    }
}

bool LeAudioClient_StartStreaming(ServiceHandle group_handle, uint16 audio_context)
{
    bool stream_start_req = FALSE;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if(client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_CAP ||
       client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_TMAP)
    {
        return FALSE;
    }

    switch (client_ctxt->mode)
    {
#ifdef INCLUDE_LE_AUDIO_UNICAST_SOURCE
        case LE_AUDIO_CLIENT_MODE_UNICAST:
            stream_start_req = leAudioClient_StartUnicastStreaming(group_handle, audio_context);
        break;
#endif /* INCLUDE_LE_AUDIO_UNICAST_SOURCE */

#ifdef INCLUDE_LE_AUDIO_BROADCAST_SOURCE
        case LE_AUDIO_CLIENT_MODE_BROADCAST:
            stream_start_req = leAudioClientBroadcast_StartStreaming(audio_context);
        break;
#endif /* INCLUDE_LE_AUDIO_BROADCAST_SOURCE */

        default:
        break;
    }

    return stream_start_req;
}

bool LeAudioClient_StartStreamingCancelRequest(ServiceHandle group_handle)
{
    bool status = FALSE;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    /* Check the below conditions
       1. Check if there is a pending unicast streaming request for the given cid
       2. We can only cancel the start request if it is in configuring the stage. Verify this by
          checking if there is an audio context pending to configure */
    if (client_ctxt->mode == LE_AUDIO_CLIENT_MODE_UNICAST &&
        !leAudioClient_IsInUnicastStreaming() &&
        client_ctxt->group_handle == group_handle &&
        client_ctxt->session_data.audio_context != CAP_CLIENT_CONTEXT_TYPE_PROHIBITED &&
        client_ctxt->requested_audio_contexts != 0)
    {
        DEBUG_LOG("LeAudioClient_StartStreamingCancelRequest");
        client_ctxt->requested_audio_contexts = 0;
        status = TRUE;
    }

    return status;
}

bool LeAudioClient_StopStreaming(ServiceHandle group_handle, bool remove_config)
{
    bool stop_streaming = FALSE;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if(client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_CAP ||
       client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_TMAP)
    {
        return FALSE;
    }

    switch (client_ctxt->mode)
    {
#ifdef INCLUDE_LE_AUDIO_UNICAST_SOURCE
        case LE_AUDIO_CLIENT_MODE_UNICAST:
            stop_streaming = leAudioClient_StopUnicastStreaming(group_handle, remove_config);
        break;
#endif /* INCLUDE_LE_AUDIO_UNICAST_SOURCE */

#ifdef INCLUDE_LE_AUDIO_BROADCAST_SOURCE
        case LE_AUDIO_CLIENT_MODE_BROADCAST:
            stop_streaming = leAudioClientBroadcast_StopStreaming(FALSE);
        break;
#endif /* INCLUDE_LE_AUDIO_BROADCAST_SOURCE */

        default:
        break;
    }

    return stop_streaming;
}

bool LeAudioClient_Disconnect(gatt_cid_t cid, bool disconnect_gracefully)
{
    bool disconnect_req_sent = FALSE;
    le_audio_client_state_t client_state = LeAudioClient_GetState();

    DEBUG_LOG_INFO("LeAudioClient_Disconnect: CID: cid=0x%04X, Client State %d, graceful %d",
                    cid, client_state, disconnect_gracefully);

    switch (client_state)
    {
        case LE_AUDIO_CLIENT_STATE_CONNECTED:
        case LE_AUDIO_CLIENT_STATE_CONNECTING:
        case LE_AUDIO_CLIENT_STATE_WAITING_FOR_QLL_TO_CONNECT:
        {
            disconnect_req_sent = TRUE;

            if (!leAudioClient_IsInPtsMode())
            {
                /* Stream stop only needs to be called for graceful disconnection only for unicast */
                if (disconnect_gracefully && leAudioClient_IsInUnicastStreaming())
                {
                    LeAudioClient_StopStreaming(leAudioClient_GetContext()->group_handle, TRUE);
                }
                else
                {
                    if (TmapClientSource_IsTmapConnected())
                    {
                        PanicFalse(TmapClientSource_DestroyInstance(leAudioClient_GetContext()->group_handle, cid));
                    }
                    else
                    {
                        CapProfileClient_DestroyInstance(leAudioClient_GetContext()->group_handle, cid);
                    }
                }

                LeAudioClient_SetState(LE_AUDIO_CLIENT_STATE_DISCONNECTING);
            }
            else
            {
                leAudioClient_HandleDisconnectRequestInPtsMode(cid);
            }
        }
        break;

        default:
        break;
    }

    if (disconnect_req_sent)
    {
        le_audio_client_context.gatt_cid = cid;
    }

    return disconnect_req_sent;
}

void LeAudioClient_ClientRegister(Task client_task)
{
    PanicNull((void *) client_task);
    TaskList_AddTask(le_audio_client_context.client_tasks, client_task);
}

void LeAudioClient_ClientUnregister(Task client_task)
{
    PanicNull((void *) client_task);
    TaskList_RemoveTask(le_audio_client_context.client_tasks, client_task);
}

bool LeAudioClient_IsUnicastStreamingActive(ServiceHandle group_handle)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    return (client_ctxt->group_handle == group_handle &&
            leAudioClient_IsInUnicastStreaming());
}

bool LeAudioClient_IsUnicastConnected(ServiceHandle group_handle)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    return (client_ctxt->group_handle == group_handle &&
            client_ctxt->state > LE_AUDIO_CLIENT_STATE_CONNECTING);
}

/*! Sets the absolute volume */
void LeAudioClient_SetAbsoluteVolume(ServiceHandle group_handle, uint8 volume)
{
    CapProfileClient_SetAbsoluteVolume(group_handle, volume);
}

uint8 LeAudioClient_GetAbsoluteVolume(ServiceHandle group_handle)
{
    volume_t volume = { 0 };
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (group_handle == client_ctxt->group_handle)
    {
        if (leAudioClient_IsInUnicastStreaming() &&
            client_ctxt->session_data.audio_context == CAP_CLIENT_CONTEXT_TYPE_CONVERSATIONAL)
        {
            volume = VoiceSources_GetVolume(voice_source_le_audio_unicast);
        }
        else
        {
            volume = AudioSources_GetVolume(audio_source_le_audio_unicast);
        }
    }

    DEBUG_LOG_INFO("LeAudioClient_GetAbsoluteVolume volume: %d", volume.value);

    return volume.value;
}

/*! Mute/Unmute the volume */
void LeAudioClient_SetMute(ServiceHandle group_handle, bool mute)
{
    CapProfileClient_SetMute(group_handle, mute);
}

void LeAudioClient_SetBootGameModeConfig(bool enable)
{
    le_audio_client_context.requested_audio_contexts = (enable ? CAP_CLIENT_CONTEXT_TYPE_GAME : CAP_CLIENT_CONTEXT_TYPE_MEDIA);
}

device_t LeAudioClient_GetDeviceForSource(generic_source_t source)
{
    bool source_found;
    device_t device = GattConnect_GetBtLeDevice(leAudioClient_GetContext()->gatt_cid);

    source_found = (source.type == source_type_audio) ? (DeviceProperties_GetAudioSource(device) == source.u.audio) :
                                                        (DeviceProperties_GetVoiceSource(device) == source.u.voice);

    if (source_found)
    {
        return device;
    }
    else
    {
        return NULL;
    }
}

bool LeAudioClient_IsAdvertFromSetMember(uint8 *adv_data, uint16 adv_data_len)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    /* In BAP mode PTS mode, just return TRUE */
    if (client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_BAP || client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_TMAP)
    {
        return TRUE;
    }

    if (LeAudioClient_GetState() == LE_AUDIO_CLIENT_STATE_CONNECTING && client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_CSIP)
    {
        return CsipClient_IsAdvertFromSetMember(adv_data, adv_data_len);
    }
    else
    {
        return CapProfileClient_IsAdvertFromSetMember(leAudioClient_GetContext()->group_handle, adv_data, adv_data_len);
    }
}

ServiceHandle LeAudioClient_GetGroupHandle(gatt_cid_t cid)
{
    ServiceHandle group_handle = INVALID_GROUP_HANDLE;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (cid == INVALID_CID || client_ctxt->gatt_cid == cid)
    {
        group_handle = client_ctxt->group_handle;
    }

    return group_handle;
}

void LeAudioClient_SetDefaultAudioContext(uint16 default_context)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    client_ctxt->requested_audio_contexts = default_context;
}

uint16 LeAudioClient_GetConfiguredCapAudioContext(void)
{
    return leAudioClient_GetContext()->configured_audio_contexts;
}

uint16 LeAudioClient_GetUnicastSessionCapAudioContext(void)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    return client_ctxt->session_data.audio_context;
}

uint32 LeAudioClient_GetConfiguredCodecId(void)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();
    le_audio_unicast_session_data_t *unicast_session_data = &client_ctxt->session_data;

    return ((uint32) unicast_session_data->codec_qos_config.codecId) |
            ((uint32) unicast_session_data->codec_qos_config.codecVersionNum << 8u) |
            ((uint32) unicast_session_data->codec_qos_config.vendorCodecId << 16u);
}

bool LeAudioClient_EnablePtsMode(le_audio_client_pts_mode_t mode)
{
    bool status = FALSE;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (client_ctxt->state == LE_AUDIO_CLIENT_STATE_INITIALIZED)
    {
        client_ctxt->pts_mode = mode;
        status = TRUE;

        if (mode == LE_AUDIO_CLIENT_PTS_MODE_CAP || mode == LE_AUDIO_CLIENT_PTS_MODE_TMAP)
        {
            LeAudioClient_SetDefaultAudioContext(0);
            TmapClientSource_SetPtsMode(1);
        }
    }

    return status;
}

void LeAudioClient_SetMode(le_audio_client_mode_t mode)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    DEBUG_LOG_INFO("LeAudioClient_SetMode mode = %d", mode);

    client_ctxt->mode = mode;
}

bool LeAudioClient_IsInUnicastMode(void)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    return client_ctxt->mode == LE_AUDIO_CLIENT_MODE_UNICAST;
}

void LeAudioClient_RegisterUsbSourceConnectParamCallback(LeAudioClient_UsbSourceParamCallback usb_src_param_cb)
{
    leAudioClient_GetContext()->usb_src_param_cb = usb_src_param_cb;
}

void LeAudioClient_StartPtsStreamingToUnicastDevices(gatt_cid_t cid_1, gatt_cid_t cid_2, bool spkr_path, bool mic_path)
{
    bap_media_config_t media_config;
    bap_microphone_config_t mic_config;
    KYMERA_INTERNAL_USB_LE_AUDIO_START_T message;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();
    uint16 frame_duration = 0;

    memset(&message, 0, sizeof(message));

    message.to_air_params.source_iso_handle = INVALID_CIS_HANDLE;
    message.to_air_params.source_iso_handle_right = INVALID_CIS_HANDLE;
    message.from_air_params.source_iso_handle = INVALID_CIS_HANDLE;
    message.from_air_params.source_iso_handle_right = INVALID_CIS_HANDLE;
    message.pts_mode = TRUE;

    /* Ensure this function is called only in PTS mode */
    PanicFalse(client_ctxt->pts_mode != LE_AUDIO_CLIENT_PTS_MODE_OFF);

    if (spkr_path)
    {
        /* Fill in To Air params */
        message.to_air_params.volume = AudioSources_CalculateOutputVolume(audio_source_le_audio_unicast);
        message.to_air_params.codec_type = KYMERA_LE_AUDIO_CODEC_LC3;
        message.to_air_params.presentation_delay = 0;
        message.to_air_params.codec_version = 0;

        if (cid_1 != INVALID_CID &&
            leAudioClient_GetSpeakerPathConfig(cid_1, (void*) &media_config) &&
            media_config.frame_length != 0)
        {
            if (media_config.source_iso_handle != INVALID_CIS_HANDLE)
            {
                message.to_air_params.source_iso_handle = media_config.source_iso_handle;
            }

            if (media_config.source_iso_handle_right != INVALID_CIS_HANDLE)
            {
                message.to_air_params.source_iso_handle_right = media_config.source_iso_handle_right;
            }
        }

        if (cid_2 != INVALID_CID &&
            leAudioClient_GetSpeakerPathConfig(cid_2, (void*) &media_config) &&
            media_config.frame_length != 0)
        {
            if (media_config.source_iso_handle != INVALID_CIS_HANDLE)
            {
                message.to_air_params.source_iso_handle = media_config.source_iso_handle;
            }

            if (media_config.source_iso_handle_right != INVALID_CIS_HANDLE)
            {
                message.to_air_params.source_iso_handle_right = media_config.source_iso_handle_right;
            }
        }

        if (message.to_air_params.source_iso_handle == INVALID_CIS_HANDLE)
        {
            message.to_air_params.source_iso_handle = message.to_air_params.source_iso_handle_right;
            message.to_air_params.source_iso_handle_right = INVALID_CIS_HANDLE;
            message.to_air_params.stream_type = KYMERA_LE_STREAM_MONO;
        }
        else if(message.to_air_params.source_iso_handle == message.to_air_params.source_iso_handle_right)
        {
            message.to_air_params.source_iso_handle_right = INVALID_CIS_HANDLE;
            message.to_air_params.stream_type = KYMERA_LE_STREAM_STEREO_USE_BOTH;
        }
        else if(message.to_air_params.source_iso_handle_right != INVALID_CIS_HANDLE)
        {
            message.to_air_params.stream_type = KYMERA_LE_STREAM_DUAL_MONO;
        }
        else
        {
            message.to_air_params.stream_type = KYMERA_LE_STREAM_MONO;
        }

        PanicFalse(message.to_air_params.source_iso_handle != INVALID_CIS_HANDLE);

        message.to_air_params.sample_rate = media_config.sample_rate;
        message.to_air_params.frame_length = media_config.frame_length;
        frame_duration = message.to_air_params.frame_duration = media_config.frame_duration;
        message.to_air_params.codec_frame_blocks_per_sdu = media_config.codec_frame_blocks_per_sdu;

        DEBUG_LOG_INFO("LeAudioClient_StartPtsStreamingToUnicastDevices To Air: sample_rate %d codec_frame_blocks_per_sdu %d, ",
                       message.to_air_params.sample_rate,
                       message.to_air_params.codec_frame_blocks_per_sdu);
        DEBUG_LOG_INFO("LeAudioClient_StartPtsStreamingToUnicastDevices To Air: frame_dur %d frame_len %d",
                        message.to_air_params.frame_duration,
                        message.to_air_params.frame_length);

        DEBUG_LOG_INFO("LeAudioClient_StartPtsStreamingToUnicastDevices To Air: Left Iso %d Right Iso %d",
                        message.to_air_params.source_iso_handle,
                        message.to_air_params.source_iso_handle_right);
    }

    if (mic_path)
    {
        message.from_air_params.codec_type = KYMERA_LE_AUDIO_CODEC_LC3;
        message.from_air_params.presentation_delay = 0;
        message.from_air_params.codec_version = 0;

        if (cid_1 != INVALID_CID &&
            leAudioClient_GetMicPathConfig(cid_1, (void*) &mic_config) &&
            mic_config.frame_length != 0)
        {
            if (mic_config.source_iso_handle != INVALID_CIS_HANDLE)
            {
                 message.from_air_params.source_iso_handle = mic_config.source_iso_handle;
            }

            if (mic_config.source_iso_handle_right != INVALID_CIS_HANDLE)
            {
                message.from_air_params.source_iso_handle_right = mic_config.source_iso_handle_right;
            }
        }

        if (cid_2 != INVALID_CID &&
            leAudioClient_GetMicPathConfig(cid_2, (void*) &mic_config) &&
            mic_config.frame_length != 0)
        {
             if (mic_config.source_iso_handle != INVALID_CIS_HANDLE)
             {
                 message.from_air_params.source_iso_handle = mic_config.source_iso_handle;
             }

             if (mic_config.source_iso_handle_right != INVALID_CIS_HANDLE)
             {
                 message.from_air_params.source_iso_handle_right = mic_config.source_iso_handle_right;
             }
        }

        if (message.from_air_params.source_iso_handle == INVALID_CIS_HANDLE)
        {
            message.from_air_params.source_iso_handle = message.from_air_params.source_iso_handle_right;
            message.from_air_params.source_iso_handle_right = INVALID_CIS_HANDLE;
        }
        else if(message.from_air_params.source_iso_handle == message.from_air_params.source_iso_handle_right)
        {
            message.from_air_params.source_iso_handle_right = INVALID_CIS_HANDLE;
        }

        PanicFalse(message.from_air_params.source_iso_handle != INVALID_CIS_HANDLE);

        message.from_air_params.codec_frame_blocks_per_sdu = mic_config.codec_frame_blocks_per_sdu;
        message.from_air_params.frame_duration = mic_config.frame_duration;
        message.from_air_params.frame_length = mic_config.frame_length;
        message.from_air_params.mic_mute_state = FALSE;
        message.from_air_params.sample_rate = mic_config.sample_rate;
        message.vbc_enabled = TRUE;

        if (!spkr_path)
        {
            frame_duration = message.from_air_params.frame_duration;
        }

        DEBUG_LOG_INFO("LeAudioClient_StartPtsStreamingToUnicastDevices From Air: sample_rate %d codec_frame_blocks_per_sdu %d, ",
                       message.from_air_params.sample_rate,
                       message.from_air_params.codec_frame_blocks_per_sdu);
        DEBUG_LOG_INFO("LeAudioClient_StartPtsStreamingToUnicastDevices From Air: frame_dur %d frame_len %d",
                        message.from_air_params.frame_duration,
                        message.from_air_params.frame_length);

        DEBUG_LOG_INFO("LeAudioClient_StartPtsStreamingToUnicastDevices From Air: Left Iso %d Right Iso %d",
                        message.from_air_params.source_iso_handle,
                        message.from_air_params.source_iso_handle_right);
    }

    client_ctxt->usb_src_param_cb(&message, mic_path, frame_duration);

    KymeraUsbLeAudio_Start(&message);
}
#endif /* defined(INCLUDE_LE_AUDIO_UNICAST_SOURCE) || defined(INCLUDE_LE_AUDIO_BROADCAST_SOURCE) */
